
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
import * as zrUtil from 'zrender/lib/core/util.js';
import * as numberUtil from '../../util/number.js';
import sliderMove from '../helper/sliderMove.js';
import { unionAxisExtentFromData } from '../../coord/axisHelper.js';
import { ensureScaleRawExtentInfo } from '../../coord/scaleRawExtentInfo.js';
import { getAxisMainType, isCoordSupported } from './helper.js';
import { SINGLE_REFERRING } from '../../util/model.js';
var each = zrUtil.each;
var asc = numberUtil.asc;
/**
 * Operate single axis.
 * One axis can only operated by one axis operator.
 * Different dataZoomModels may be defined to operate the same axis.
 * (i.e. 'inside' data zoom and 'slider' data zoom components)
 * So dataZoomModels share one axisProxy in that case.
 */

var AxisProxy =
/** @class */
function () {
	function AxisProxy(dimName, axisIndex, dataZoomModel, ecModel) {
		this._dimName = dimName;
		this._axisIndex = axisIndex;
		this.ecModel = ecModel;
		this._dataZoomModel = dataZoomModel; // /**
		//  * @readOnly
		//  * @private
		//  */
		// this.hasSeriesStacked;
	}
	/**
   * Whether the axisProxy is hosted by dataZoomModel.
   */

	AxisProxy.prototype.hostedBy = function (dataZoomModel) {
		return this._dataZoomModel === dataZoomModel;
	};
	/**
   * @return Value can only be NaN or finite value.
   */

	AxisProxy.prototype.getDataValueWindow = function () {
		return this._valueWindow.slice();
	};
	/**
   * @return {Array.<number>}
   */

	AxisProxy.prototype.getDataPercentWindow = function () {
		return this._percentWindow.slice();
	};

	AxisProxy.prototype.getTargetSeriesModels = function () {
		var seriesModels = [];
		this.ecModel.eachSeries(function (seriesModel) {
			if (isCoordSupported(seriesModel)) {
				var axisMainType = getAxisMainType(this._dimName);
				var axisModel = seriesModel.getReferringComponents(axisMainType, SINGLE_REFERRING).models[0];

				if (axisModel && this._axisIndex === axisModel.componentIndex) {
					seriesModels.push(seriesModel);
				}
			}
		}, this);
		return seriesModels;
	};

	AxisProxy.prototype.getAxisModel = function () {
		return this.ecModel.getComponent(this._dimName + 'Axis', this._axisIndex);
	};

	AxisProxy.prototype.getMinMaxSpan = function () {
		return zrUtil.clone(this._minMaxSpan);
	};
	/**
   * Only calculate by given range and this._dataExtent, do not change anything.
   */

	AxisProxy.prototype.calculateDataWindow = function (opt) {
		var dataExtent = this._dataExtent;
		var axisModel = this.getAxisModel();
		var scale = axisModel.axis.scale;

		var rangePropMode = this._dataZoomModel.getRangePropMode();

		var percentExtent = [0, 100];
		var percentWindow = [];
		var valueWindow = [];
		var hasPropModeValue;
		each(['start', 'end'], function (prop, idx) {
			var boundPercent = opt[prop];
			var boundValue = opt[prop + 'Value']; // Notice: dataZoom is based either on `percentProp` ('start', 'end') or
			// on `valueProp` ('startValue', 'endValue'). (They are based on the data extent
			// but not min/max of axis, which will be calculated by data window then).
			// The former one is suitable for cases that a dataZoom component controls multiple
			// axes with different unit or extent, and the latter one is suitable for accurate
			// zoom by pixel (e.g., in dataZoomSelect).
			// we use `getRangePropMode()` to mark which prop is used. `rangePropMode` is updated
			// only when setOption or dispatchAction, otherwise it remains its original value.
			// (Why not only record `percentProp` and always map to `valueProp`? Because
			// the map `valueProp` -> `percentProp` -> `valueProp` probably not the original
			// `valueProp`. consider two axes constrolled by one dataZoom. They have different
			// data extent. All of values that are overflow the `dataExtent` will be calculated
			// to percent '100%').

			if (rangePropMode[idx] === 'percent') {
				boundPercent == null && (boundPercent = percentExtent[idx]); // Use scale.parse to math round for category or time axis.

				boundValue = scale.parse(numberUtil.linearMap(boundPercent, percentExtent, dataExtent));
			} else {
				hasPropModeValue = true;
				boundValue = boundValue == null ? dataExtent[idx] : scale.parse(boundValue); // Calculating `percent` from `value` may be not accurate, because
				// This calculation can not be inversed, because all of values that
				// are overflow the `dataExtent` will be calculated to percent '100%'

				boundPercent = numberUtil.linearMap(boundValue, dataExtent, percentExtent);
			} // valueWindow[idx] = round(boundValue);
			// percentWindow[idx] = round(boundPercent);
			// fallback to extent start/end when parsed value or percent is invalid

			valueWindow[idx] = boundValue == null || isNaN(boundValue) ? dataExtent[idx] : boundValue;
			percentWindow[idx] = boundPercent == null || isNaN(boundPercent) ? percentExtent[idx] : boundPercent;
		});
		asc(valueWindow);
		asc(percentWindow); // The windows from user calling of `dispatchAction` might be out of the extent,
		// or do not obey the `min/maxSpan`, `min/maxValueSpan`. But we don't restrict window
		// by `zoomLock` here, because we see `zoomLock` just as a interaction constraint,
		// where API is able to initialize/modify the window size even though `zoomLock`
		// specified.

		var spans = this._minMaxSpan;
		hasPropModeValue ? restrictSet(valueWindow, percentWindow, dataExtent, percentExtent, false) : restrictSet(percentWindow, valueWindow, percentExtent, dataExtent, true);

		function restrictSet(fromWindow, toWindow, fromExtent, toExtent, toValue) {
			var suffix = toValue ? 'Span' : 'ValueSpan';
			sliderMove(0, fromWindow, fromExtent, 'all', spans['min' + suffix], spans['max' + suffix]);

			for (var i = 0; i < 2; i++) {
				toWindow[i] = numberUtil.linearMap(fromWindow[i], fromExtent, toExtent, true);
				toValue && (toWindow[i] = scale.parse(toWindow[i]));
			}
		}

		return {
			valueWindow: valueWindow,
			percentWindow: percentWindow
		};
	};
	/**
   * Notice: reset should not be called before series.restoreData() is called,
   * so it is recommended to be called in "process stage" but not "model init
   * stage".
   */

	AxisProxy.prototype.reset = function (dataZoomModel) {
		if (dataZoomModel !== this._dataZoomModel) {
			return;
		}

		var targetSeries = this.getTargetSeriesModels(); // Culculate data window and data extent, and record them.

		this._dataExtent = calculateDataExtent(this, this._dimName, targetSeries); // `calculateDataWindow` uses min/maxSpan.

		this._updateMinMaxSpan();

		var dataWindow = this.calculateDataWindow(dataZoomModel.settledOption);
		this._valueWindow = dataWindow.valueWindow;
		this._percentWindow = dataWindow.percentWindow; // Update axis setting then.

		this._setAxisModel();
	};

	AxisProxy.prototype.filterData = function (dataZoomModel, api) {
		if (dataZoomModel !== this._dataZoomModel) {
			return;
		}

		var axisDim = this._dimName;
		var seriesModels = this.getTargetSeriesModels();
		var filterMode = dataZoomModel.get('filterMode');
		var valueWindow = this._valueWindow;

		if (filterMode === 'none') {
			return;
		} // FIXME
		// Toolbox may has dataZoom injected. And if there are stacked bar chart
		// with NaN data, NaN will be filtered and stack will be wrong.
		// So we need to force the mode to be set empty.
		// In fect, it is not a big deal that do not support filterMode-'filter'
		// when using toolbox#dataZoom, utill tooltip#dataZoom support "single axis
		// selection" some day, which might need "adapt to data extent on the
		// otherAxis", which is disabled by filterMode-'empty'.
		// But currently, stack has been fixed to based on value but not index,
		// so this is not an issue any more.
		// let otherAxisModel = this.getOtherAxisModel();
		// if (dataZoomModel.get('$fromToolbox')
		//     && otherAxisModel
		//     && otherAxisModel.hasSeriesStacked
		// ) {
		//     filterMode = 'empty';
		// }
		// TODO
		// filterMode 'weakFilter' and 'empty' is not optimized for huge data yet.

		each(seriesModels, function (seriesModel) {
			var seriesData = seriesModel.getData();
			var dataDims = seriesData.mapDimensionsAll(axisDim);

			if (!dataDims.length) {
				return;
			}

			if (filterMode === 'weakFilter') {
				var store_1 = seriesData.getStore();
				var dataDimIndices_1 = zrUtil.map(dataDims, function (dim) {
					return seriesData.getDimensionIndex(dim);
				}, seriesData);
				seriesData.filterSelf(function (dataIndex) {
					var leftOut;
					var rightOut;
					var hasValue;

					for (var i = 0; i < dataDims.length; i++) {
						var value = store_1.get(dataDimIndices_1[i], dataIndex);
						var thisHasValue = !isNaN(value);
						var thisLeftOut = value < valueWindow[0];
						var thisRightOut = value > valueWindow[1];

						if (thisHasValue && !thisLeftOut && !thisRightOut) {
							return true;
						}

						thisHasValue && (hasValue = true);
						thisLeftOut && (leftOut = true);
						thisRightOut && (rightOut = true);
					} // If both left out and right out, do not filter.

					return hasValue && leftOut && rightOut;
				});
			} else {
				each(dataDims, function (dim) {
					if (filterMode === 'empty') {
						seriesModel.setData(seriesData = seriesData.map(dim, function (value) {
							return !isInWindow(value) ? NaN : value;
						}));
					} else {
						var range = {};
						range[dim] = valueWindow; // console.time('select');

						seriesData.selectRange(range); // console.timeEnd('select');
					}
				});
			}

			each(dataDims, function (dim) {
				seriesData.setApproximateExtent(valueWindow, dim);
			});
		});

		function isInWindow(value) {
			return value >= valueWindow[0] && value <= valueWindow[1];
		}
	};

	AxisProxy.prototype._updateMinMaxSpan = function () {
		var minMaxSpan = this._minMaxSpan = {};
		var dataZoomModel = this._dataZoomModel;
		var dataExtent = this._dataExtent;
		each(['min', 'max'], function (minMax) {
			var percentSpan = dataZoomModel.get(minMax + 'Span');
			var valueSpan = dataZoomModel.get(minMax + 'ValueSpan');
			valueSpan != null && (valueSpan = this.getAxisModel().axis.scale.parse(valueSpan)); // minValueSpan and maxValueSpan has higher priority than minSpan and maxSpan

			if (valueSpan != null) {
				percentSpan = numberUtil.linearMap(dataExtent[0] + valueSpan, dataExtent, [0, 100], true);
			} else if (percentSpan != null) {
				valueSpan = numberUtil.linearMap(percentSpan, [0, 100], dataExtent, true) - dataExtent[0];
			}

			minMaxSpan[minMax + 'Span'] = percentSpan;
			minMaxSpan[minMax + 'ValueSpan'] = valueSpan;
		}, this);
	};

	AxisProxy.prototype._setAxisModel = function () {
		var axisModel = this.getAxisModel();
		var percentWindow = this._percentWindow;
		var valueWindow = this._valueWindow;

		if (!percentWindow) {
			return;
		} // [0, 500]: arbitrary value, guess axis extent.

		var precision = numberUtil.getPixelPrecision(valueWindow, [0, 500]);
		precision = Math.min(precision, 20); // For value axis, if min/max/scale are not set, we just use the extent obtained
		// by series data, which may be a little different from the extent calculated by
		// `axisHelper.getScaleExtent`. But the different just affects the experience a
		// little when zooming. So it will not be fixed until some users require it strongly.

		var rawExtentInfo = axisModel.axis.scale.rawExtentInfo;

		if (percentWindow[0] !== 0) {
			rawExtentInfo.setDeterminedMinMax('min', +valueWindow[0].toFixed(precision));
		}

		if (percentWindow[1] !== 100) {
			rawExtentInfo.setDeterminedMinMax('max', +valueWindow[1].toFixed(precision));
		}

		rawExtentInfo.freeze();
	};

	return AxisProxy;
}();

function calculateDataExtent(axisProxy, axisDim, seriesModels) {
	var dataExtent = [Infinity, -Infinity];
	each(seriesModels, function (seriesModel) {
		unionAxisExtentFromData(dataExtent, seriesModel.getData(), axisDim);
	}); // It is important to get "consistent" extent when more then one axes is
	// controlled by a `dataZoom`, otherwise those axes will not be synchronized
	// when zooming. But it is difficult to know what is "consistent", considering
	// axes have different type or even different meanings (For example, two
	// time axes are used to compare data of the same date in different years).
	// So basically dataZoom just obtains extent by series.data (in category axis
	// extent can be obtained from axis.data).
	// Nevertheless, user can set min/max/scale on axes to make extent of axes
	// consistent.

	var axisModel = axisProxy.getAxisModel();
	var rawExtentResult = ensureScaleRawExtentInfo(axisModel.axis.scale, axisModel, dataExtent).calculate();
	return [rawExtentResult.min, rawExtentResult.max];
}

export default AxisProxy;